1use std::num::NonZeroIsize;
2
3use crate::common::{ChildKillablePointer, KillablePointer};
4use crate::generic::{EditSection, ReadSection};
5
6#[derive(Debug)]
15pub struct EditHandle {
16 pub(crate) internal: *mut aviutl2_sys::plugin2::EDIT_HANDLE,
17 pub(crate) is_ready: std::sync::Arc<std::sync::atomic::AtomicBool>,
18}
19
20unsafe impl Send for EditHandle {}
21unsafe impl Sync for EditHandle {}
22
23#[derive(thiserror::Error, Debug)]
25pub enum EditHandleError {
26 #[error("api call failed")]
27 ApiCallFailed,
28 #[error("effect does not exist")]
29 EffectNotFound,
30 #[error("input utf-16 string contains null byte")]
31 InputCwstrContainsNull(#[from] crate::common::NullByteError),
32 #[error("unknown edit state: {0}")]
33 UnknownEditState(i32),
34}
35
36impl EditHandle {
37 pub(crate) unsafe fn new(
38 internal: *mut aviutl2_sys::plugin2::EDIT_HANDLE,
39 is_ready: std::sync::Arc<std::sync::atomic::AtomicBool>,
40 ) -> Self {
41 Self { internal, is_ready }
42 }
43
44 pub fn is_ready(&self) -> bool {
46 self.is_ready.load(std::sync::atomic::Ordering::Acquire)
47 }
48
49 pub fn call_edit_section<'a, T, F>(&self, callback: F) -> Result<T, EditHandleError>
55 where
56 T: Send + 'static,
57 F: FnOnce(&mut EditSection) -> T + Send + 'a,
58 {
59 assert!(
60 self.is_ready(),
61 "call_edit_section cannot be called before register_plugin is done"
62 );
63
64 type CallbackParam<'a, F, T> = (ChildKillablePointer<Option<F>>, &'a mut Option<T>);
65
66 let closure = Some(callback);
67 let param = KillablePointer::new(closure);
68 let child_param = param.create_child();
69
70 extern "C" fn trampoline<F, T>(
71 param: *mut std::ffi::c_void,
72 edit_section: *mut aviutl2_sys::plugin2::EDIT_SECTION,
73 ) where
74 T: Send + 'static,
75 F: FnOnce(&mut EditSection) -> T,
76 {
77 unsafe {
78 let (child_param, result_ptr) = &mut *(param as *mut CallbackParam<F, T>);
79 let callback = child_param.as_mut().take().expect("Callback already taken");
80 let mut edit_section = EditSection::from_raw(edit_section);
81 let res = callback(&mut edit_section);
82 result_ptr.replace(res);
83 }
84 }
85
86 let trampoline_static = trampoline::<F, T>
87 as extern "C" fn(*mut std::ffi::c_void, *mut aviutl2_sys::plugin2::EDIT_SECTION);
88
89 let mut result = None;
90 let param = Box::<CallbackParam<F, T>>::new((child_param, &mut result));
91 let param_ptr = Box::into_raw(param);
92
93 let success = unsafe {
94 ((*self.internal).call_edit_section_param)(
95 param_ptr as *mut std::ffi::c_void,
96 trampoline_static,
97 )
98 };
99
100 drop(unsafe { Box::from_raw(param_ptr) });
101
102 if success {
103 Ok(result.expect("Callback did not set result"))
104 } else {
105 Err(EditHandleError::ApiCallFailed)
106 }
107 }
108
109 pub fn call_read_section<'a, T, F>(&self, callback: F) -> Result<T, EditHandleError>
115 where
116 T: Send + 'static,
117 F: FnOnce(&ReadSection) -> T + Send + 'a,
118 {
119 assert!(
120 self.is_ready(),
121 "call_read_section cannot be called before register_plugin is done"
122 );
123
124 type CallbackParam<'a, F, T> = (ChildKillablePointer<Option<F>>, &'a mut Option<T>);
125
126 let closure = Some(callback);
127 let param = KillablePointer::new(closure);
128 let child_param = param.create_child();
129
130 extern "C" fn trampoline<F, T>(
131 param: *mut std::ffi::c_void,
132 read_section: *mut aviutl2_sys::plugin2::EDIT_SECTION,
133 ) where
134 T: Send + 'static,
135 F: FnOnce(&ReadSection) -> T,
136 {
137 unsafe {
138 let (child_param, result_ptr) = &mut *(param as *mut CallbackParam<F, T>);
139 let callback = child_param.as_mut().take().expect("Callback already taken");
140 let read_section = ReadSection::from_raw(read_section);
141 let res = callback(&read_section);
142 result_ptr.replace(res);
143 }
144 }
145
146 let trampoline_static = trampoline::<F, T>
147 as extern "C" fn(*mut std::ffi::c_void, *mut aviutl2_sys::plugin2::EDIT_SECTION);
148
149 let mut result = None;
150 let param = Box::<CallbackParam<F, T>>::new((child_param, &mut result));
151 let param_ptr = Box::into_raw(param);
152
153 let success = unsafe {
154 ((*self.internal).call_read_section_param)(
155 param_ptr as *mut std::ffi::c_void,
156 trampoline_static,
157 )
158 };
159
160 drop(unsafe { Box::from_raw(param_ptr) });
161
162 if success {
163 Ok(result.expect("Callback did not set result"))
164 } else {
165 Err(EditHandleError::ApiCallFailed)
166 }
167 }
168
169 pub fn get_edit_info(&self) -> crate::generic::EditInfo {
171 assert!(
172 self.is_ready(),
173 "get_edit_info cannot be called before register_plugin is done"
174 );
175 let mut raw_info = std::mem::MaybeUninit::<aviutl2_sys::plugin2::EDIT_INFO>::uninit();
176 unsafe {
177 ((*self.internal).get_edit_info)(
178 raw_info.as_mut_ptr(),
179 std::mem::size_of::<aviutl2_sys::plugin2::EDIT_INFO>() as _,
180 );
181 let edit_info = raw_info.assume_init();
182 crate::generic::EditInfo::from_raw(&edit_info)
183 }
184 }
185
186 pub fn restart_host_app(&self) {
188 assert!(
189 self.is_ready(),
190 "restart_host_app cannot be called before register_plugin is done"
191 );
192 unsafe {
193 ((*self.internal).restart_host_app)();
194 }
195 }
196
197 pub fn enumerate_effects<F>(&self, callback: F)
203 where
204 F: FnMut(Effect),
205 {
206 assert!(
207 self.is_ready(),
208 "enumerate_effects cannot be called before register_plugin is done"
209 );
210 type CallbackParam<F> = ChildKillablePointer<F>;
211
212 extern "C" fn trampoline<F>(
213 param: *mut std::ffi::c_void,
214 name: aviutl2_sys::common::LPCWSTR,
215 r#type: i32,
216 flag: i32,
217 ) where
218 F: FnMut(Effect),
219 {
220 let callback = unsafe { &mut *(param as *mut CallbackParam<F>) };
221 let callback = unsafe { callback.as_mut() };
222 let name_str = unsafe { crate::common::load_wide_string(name) };
223 if let Ok(effect_type) = EffectType::try_from(r#type) {
224 let effect = Effect {
225 name: name_str,
226 effect_type,
227 flag: EffectFlag::from_bits(flag),
228 };
229 callback(effect);
230 } else {
231 tracing::warn!("Unknown effect type: {}", r#type);
232 }
233 }
234
235 let trampoline_static = trampoline::<F>
236 as extern "C" fn(*mut std::ffi::c_void, aviutl2_sys::common::LPCWSTR, i32, i32);
237 let callback_guard = KillablePointer::new(callback);
238 let child_param = callback_guard.create_child();
239 let param = Box::new(child_param);
240 let param_ptr = Box::into_raw(param);
241 unsafe {
242 ((*self.internal).enum_effect_name)(
243 param_ptr as *mut std::ffi::c_void,
244 trampoline_static,
245 );
246 }
247 drop(unsafe { Box::from_raw(param_ptr) });
248 }
249
250 pub fn get_effects(&self) -> Vec<Effect> {
252 assert!(
253 self.is_ready(),
254 "get_effects cannot be called before register_plugin is done"
255 );
256 let mut effects = Vec::new();
257 self.enumerate_effects(|effect| {
258 effects.push(effect);
259 });
260 effects
261 }
262
263 pub fn enumerate_effect_items<F>(
273 &self,
274 effect: &str,
275 callback: F,
276 ) -> Result<(), EditHandleError>
277 where
278 F: FnMut(EffectItemInfo),
279 {
280 assert!(
281 self.is_ready(),
282 "enumerate_effect_items cannot be called before register_plugin is done"
283 );
284 type CallbackParam<F> = ChildKillablePointer<F>;
285
286 unsafe extern "C" fn trampoline<F>(
287 param: *mut std::ffi::c_void,
288 name: aviutl2_sys::common::LPCWSTR,
289 r#type: i32,
290 ) where
291 F: FnMut(EffectItemInfo),
292 {
293 let callback = unsafe { &mut *(param as *mut CallbackParam<F>) };
294 let callback = unsafe { callback.as_mut() };
295 let name = unsafe { crate::common::load_wide_string(name) };
296 if let Some(info) = effect_item_info_from_raw(name, r#type) {
297 callback(info);
298 }
299 }
300
301 let effect = crate::common::CWString::new(effect)?;
302 let trampoline_static = trampoline::<F>
303 as unsafe extern "C" fn(*mut std::ffi::c_void, aviutl2_sys::common::LPCWSTR, i32);
304 let callback_guard = KillablePointer::new(callback);
305 let child_param = callback_guard.create_child();
306 let param = Box::new(child_param);
307 let param_ptr = Box::into_raw(param);
308 let success = unsafe {
309 ((*self.internal).enum_effect_item)(
310 effect.as_ptr(),
311 param_ptr as *mut std::ffi::c_void,
312 trampoline_static,
313 )
314 };
315 drop(unsafe { Box::from_raw(param_ptr) });
316
317 if success {
318 Ok(())
319 } else {
320 Err(EditHandleError::EffectNotFound)
321 }
322 }
323
324 pub fn get_effect_items(&self, effect: &str) -> Result<Vec<EffectItemInfo>, EditHandleError> {
330 assert!(
331 self.is_ready(),
332 "get_effect_items cannot be called before register_plugin is done"
333 );
334 let mut items = Vec::new();
335 self.enumerate_effect_items(effect, |item| {
336 items.push(item);
337 })?;
338 Ok(items)
339 }
340
341 pub fn enumerate_modules<F>(&self, callback: F)
343 where
344 F: FnMut(ModuleInfo),
345 {
346 assert!(
347 self.is_ready(),
348 "enumerate_modules cannot be called before register_plugin is done"
349 );
350 type CallbackParam<F> = ChildKillablePointer<F>;
351
352 extern "C" fn trampoline<F>(
353 param: *mut std::ffi::c_void,
354 module: *mut aviutl2_sys::plugin2::MODULE_INFO,
355 ) where
356 F: FnMut(ModuleInfo),
357 {
358 let callback = unsafe { &mut *(param as *mut CallbackParam<F>) };
359 let callback = unsafe { callback.as_mut() };
360 if let Some(module_info) = module_info_from_raw(module) {
361 callback(module_info);
362 }
363 }
364 let trampoline_static = trampoline::<F>
365 as unsafe extern "C" fn(*mut std::ffi::c_void, *mut aviutl2_sys::plugin2::MODULE_INFO);
366 let callback_guard = KillablePointer::new(callback);
367 let child_param = callback_guard.create_child();
368 let param = Box::new(child_param);
369 let param_ptr = Box::into_raw(param);
370 unsafe {
371 ((*self.internal).enum_module_info)(
372 param_ptr as *mut std::ffi::c_void,
373 trampoline_static,
374 );
375 }
376 drop(unsafe { Box::from_raw(param_ptr) });
377 }
378
379 pub fn get_modules(&self) -> Vec<ModuleInfo> {
381 assert!(
382 self.is_ready(),
383 "get_modules cannot be called before register_plugin is done"
384 );
385 let mut modules = Vec::new();
386 self.enumerate_modules(|module| {
387 modules.push(module);
388 });
389 modules
390 }
391
392 pub fn get_host_app_window_raw(&self) -> Option<raw_window_handle::Win32WindowHandle> {
394 let hwnd = unsafe { ((*self.internal).get_host_app_window)() };
395 NonZeroIsize::new(hwnd as isize).map(raw_window_handle::Win32WindowHandle::new)
396 }
397
398 pub unsafe fn get_host_app_window(&'_ self) -> Option<raw_window_handle::WindowHandle<'_>> {
404 self.get_host_app_window_raw().map(|handle| unsafe {
405 raw_window_handle::WindowHandle::borrow_raw(raw_window_handle::RawWindowHandle::Win32(
406 handle,
407 ))
408 })
409 }
410
411 pub fn get_edit_state(&self) -> Result<EditState, EditHandleError> {
413 assert!(
414 self.is_ready(),
415 "get_edit_state cannot be called before register_plugin is done"
416 );
417 let state = unsafe { ((*self.internal).get_edit_state)() };
418 EditState::try_from(state).map_err(|_| EditHandleError::UnknownEditState(state))
419 }
420}
421
422#[derive(Debug, Clone, PartialEq, Eq)]
424pub struct Effect {
425 pub name: String,
427 pub effect_type: EffectType,
429 pub flag: EffectFlag,
431}
432
433#[derive(Debug, Clone, PartialEq, Eq)]
435pub struct EffectItemInfo {
436 pub name: String,
438 pub item_type: EffectItemType,
440}
441
442#[derive(Debug, Clone, Copy, PartialEq, Eq)]
444pub enum EffectType {
445 Filter,
447 Input,
449 SceneChange,
451 Control,
453 Output,
455}
456
457#[derive(Debug, Clone, Copy, PartialEq, Eq)]
459pub enum EffectItemType {
460 Integer,
462 Number,
464 Check,
466 Text,
468 String,
470 File,
472 Color,
474 Select,
476 Scene,
478 Range,
480 Combo,
482 Mask,
484 Font,
486 Figure,
488 Data,
490 Folder,
492}
493
494define_bitflag! {
495 #[derive(Default, Clone, Copy, Debug, PartialEq, Eq, Hash)]
497 #[non_exhaustive]
498 pub struct EffectFlag: i32 {
499 video: aviutl2_sys::plugin2::EDIT_HANDLE::EFFECT_FLAG_VIDEO,
501
502 audio: aviutl2_sys::plugin2::EDIT_HANDLE::EFFECT_FLAG_AUDIO,
504
505 as_filter: aviutl2_sys::plugin2::EDIT_HANDLE::EFFECT_FLAG_FILTER,
507
508 camera: aviutl2_sys::plugin2::EDIT_HANDLE::EFFECT_FLAG_CAMERA,
510 }
511}
512
513impl TryFrom<i32> for EffectType {
514 type Error = ();
515
516 fn try_from(value: i32) -> Result<Self, Self::Error> {
517 match value {
518 1 => Ok(EffectType::Filter),
519 2 => Ok(EffectType::Input),
520 3 => Ok(EffectType::SceneChange),
521 4 => Ok(EffectType::Control),
522 5 => Ok(EffectType::Output),
523 _ => Err(()),
524 }
525 }
526}
527impl From<EffectType> for i32 {
528 fn from(value: EffectType) -> Self {
529 match value {
530 EffectType::Filter => 1,
531 EffectType::Input => 2,
532 EffectType::SceneChange => 3,
533 EffectType::Control => 4,
534 EffectType::Output => 5,
535 }
536 }
537}
538
539impl TryFrom<i32> for EffectItemType {
540 type Error = ();
541
542 fn try_from(value: i32) -> Result<Self, Self::Error> {
543 match value {
544 1 => Ok(EffectItemType::Integer),
545 2 => Ok(EffectItemType::Number),
546 3 => Ok(EffectItemType::Check),
547 4 => Ok(EffectItemType::Text),
548 5 => Ok(EffectItemType::String),
549 6 => Ok(EffectItemType::File),
550 7 => Ok(EffectItemType::Color),
551 8 => Ok(EffectItemType::Select),
552 9 => Ok(EffectItemType::Scene),
553 10 => Ok(EffectItemType::Range),
554 11 => Ok(EffectItemType::Combo),
555 12 => Ok(EffectItemType::Mask),
556 13 => Ok(EffectItemType::Font),
557 14 => Ok(EffectItemType::Figure),
558 15 => Ok(EffectItemType::Data),
559 16 => Ok(EffectItemType::Folder),
560 _ => Err(()),
561 }
562 }
563}
564impl From<EffectItemType> for i32 {
565 fn from(value: EffectItemType) -> Self {
566 match value {
567 EffectItemType::Integer => 1,
568 EffectItemType::Number => 2,
569 EffectItemType::Check => 3,
570 EffectItemType::Text => 4,
571 EffectItemType::String => 5,
572 EffectItemType::File => 6,
573 EffectItemType::Color => 7,
574 EffectItemType::Select => 8,
575 EffectItemType::Scene => 9,
576 EffectItemType::Range => 10,
577 EffectItemType::Combo => 11,
578 EffectItemType::Mask => 12,
579 EffectItemType::Font => 13,
580 EffectItemType::Figure => 14,
581 EffectItemType::Data => 15,
582 EffectItemType::Folder => 16,
583 }
584 }
585}
586
587#[derive(Debug, Clone, PartialEq, Eq)]
589pub struct ModuleInfo {
590 pub module_type: ModuleType,
592 pub name: String,
594 pub information: String,
596}
597
598#[derive(Debug, Clone, Copy, PartialEq, Eq)]
600pub enum ModuleType {
601 ScriptFilter,
603 ScriptObject,
605 ScriptCamera,
607 ScriptTrack,
609 ScriptModule,
611 PluginInput,
613 PluginOutput,
615 PluginFilter,
617 PluginGeneric,
619}
620
621impl TryFrom<i32> for ModuleType {
622 type Error = ();
623
624 fn try_from(value: i32) -> Result<Self, Self::Error> {
625 match value {
626 1 => Ok(ModuleType::ScriptFilter),
627 2 => Ok(ModuleType::ScriptObject),
628 3 => Ok(ModuleType::ScriptCamera),
629 4 => Ok(ModuleType::ScriptTrack),
630 5 => Ok(ModuleType::ScriptModule),
631 6 => Ok(ModuleType::PluginInput),
632 7 => Ok(ModuleType::PluginOutput),
633 8 => Ok(ModuleType::PluginFilter),
634 9 => Ok(ModuleType::PluginGeneric),
635 _ => Err(()),
636 }
637 }
638}
639impl From<ModuleType> for i32 {
640 fn from(value: ModuleType) -> Self {
641 match value {
642 ModuleType::ScriptFilter => 1,
643 ModuleType::ScriptObject => 2,
644 ModuleType::ScriptCamera => 3,
645 ModuleType::ScriptTrack => 4,
646 ModuleType::ScriptModule => 5,
647 ModuleType::PluginInput => 6,
648 ModuleType::PluginOutput => 7,
649 ModuleType::PluginFilter => 8,
650 ModuleType::PluginGeneric => 9,
651 }
652 }
653}
654
655#[derive(Debug, Clone, Copy, PartialEq, Eq)]
657pub enum EditState {
658 Edit,
660 Preview,
662 Save,
664}
665
666impl TryFrom<i32> for EditState {
667 type Error = ();
668
669 fn try_from(value: i32) -> Result<Self, Self::Error> {
670 match value {
671 0 => Ok(EditState::Edit),
672 1 => Ok(EditState::Preview),
673 2 => Ok(EditState::Save),
674 _ => Err(()),
675 }
676 }
677}
678impl From<EditState> for i32 {
679 fn from(value: EditState) -> Self {
680 match value {
681 EditState::Edit => 0,
682 EditState::Preview => 1,
683 EditState::Save => 2,
684 }
685 }
686}
687
688#[derive(Debug)]
692pub struct GlobalEditHandle {
693 edit_handle: std::sync::OnceLock<crate::generic::EditHandle>,
694}
695
696impl GlobalEditHandle {
697 pub const fn new() -> Self {
699 Self {
700 edit_handle: std::sync::OnceLock::new(),
701 }
702 }
703
704 pub fn init(&self, edit_handle: crate::generic::EditHandle) {
706 let _ = self
707 .edit_handle
708 .set(edit_handle)
709 .map_err(|_| tracing::warn!("GlobalEditHandle was already initialized"));
710 }
711
712 pub fn is_ready(&self) -> bool {
714 self.edit_handle
715 .get()
716 .is_some_and(|handle| handle.is_ready())
717 }
718}
719
720impl Default for GlobalEditHandle {
721 fn default() -> Self {
722 Self::new()
723 }
724}
725
726impl std::ops::Deref for GlobalEditHandle {
727 type Target = crate::generic::EditHandle;
728
729 fn deref(&self) -> &Self::Target {
730 self.edit_handle
731 .get()
732 .expect("GlobalEditHandle is not initialized")
733 }
734}
735
736fn effect_item_info_from_raw(name: String, item_type: i32) -> Option<EffectItemInfo> {
737 if let Ok(item_type) = EffectItemType::try_from(item_type) {
738 Some(EffectItemInfo { name, item_type })
739 } else {
740 tracing::warn!("Unknown effect item type: {}", item_type);
741 None
742 }
743}
744
745fn module_info_from_raw(raw: *mut aviutl2_sys::plugin2::MODULE_INFO) -> Option<ModuleInfo> {
746 let module_type = unsafe { (*raw).r#type };
747 if let Ok(module_type) = ModuleType::try_from(module_type) {
748 Some(ModuleInfo {
749 module_type,
750 name: unsafe { crate::common::load_wide_string((*raw).name) },
751 information: unsafe { crate::common::load_wide_string((*raw).information) },
752 })
753 } else {
754 tracing::warn!("Unknown module type: {}", module_type);
755 None
756 }
757}
758
759#[cfg(test)]
760mod tests {
761 use super::*;
762
763 #[test]
764 fn effect_item_type_try_from_known_values() {
765 assert_eq!(EffectItemType::try_from(1), Ok(EffectItemType::Integer));
766 assert_eq!(EffectItemType::try_from(8), Ok(EffectItemType::Select));
767 assert_eq!(EffectItemType::try_from(16), Ok(EffectItemType::Folder));
768 }
769
770 #[test]
771 fn effect_item_type_try_from_unknown_value_fails() {
772 assert_eq!(EffectItemType::try_from(999), Err(()));
773 }
774
775 #[test]
776 fn effect_item_type_into_i32() {
777 assert_eq!(i32::from(EffectItemType::Integer), 1);
778 assert_eq!(i32::from(EffectItemType::Combo), 11);
779 assert_eq!(i32::from(EffectItemType::Folder), 16);
780 }
781
782 #[test]
783 fn effect_item_info_from_raw_returns_none_for_unknown_type() {
784 assert_eq!(effect_item_info_from_raw("test".to_string(), 999), None);
785 }
786
787 #[test]
788 fn effect_item_info_from_raw_builds_info_for_known_type() {
789 assert_eq!(
790 effect_item_info_from_raw("test".to_string(), 4),
791 Some(EffectItemInfo {
792 name: "test".to_string(),
793 item_type: EffectItemType::Text,
794 })
795 );
796 }
797
798 #[test]
799 fn module_type_try_from_unknown_value_fails() {
800 assert_eq!(ModuleType::try_from(999), Err(()));
801 }
802
803 #[test]
804 fn edit_state_try_from_unknown_value_fails() {
805 assert_eq!(EditState::try_from(999), Err(()));
806 }
807}